﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections;
using System.Reflection;
using System.IO;
using System.Diagnostics;

namespace CSharpCompiler
{
    public class Compiler
    {
        //Delegate für Events
        public delegate void CompilerResultsEventHandler(Object sender, CompilerResultsEventArgs e);

        //Events für Compiler
        public event CompilerResultsEventHandler CompileComplete;   //Kompiliert
        public event CompilerResultsEventHandler CompileError;      //Nicht Kompiliert

        
        private CodeCompileUnit codeCompileUnit;
        private CodeNamespace codeNamespace;
        private CodeTypeDeclaration codeClass;
        
        CodeMemberMethod entryPoint;
        CompilerResults compilerResults;

        //DLLs
        public ArrayList libraries;
        public ArrayList modules;
        //Entrypoint - Code. Muss vor dem Kompilieren gesetzt werden.
        public String MainMethodCode
        {
            get;
            set;
        }

        //Collection mit Using - Direktiven. kein Ersatz für die libraries, die physikalisch als DLLs vorhanden sein müssen
        public CodeNamespaceImportCollection Usings
        {
            get
            {
                return codeNamespace.Imports;
            }
        }

        public List<CodeMemberMethod> MemberMethods
        {
            get;
            set;
        }
        

        /// <summary>
        /// Konstruktor. Hier wird der Dom - Baum zum Großteil zusammengestellt.
        /// Außerdem werden Assemblies der aktuellen Domäne hinzugefügt
        /// </summary>
        public Compiler()
        {

            //codeCompileUnit definieren und Namespace erzeugen
            codeCompileUnit = new CodeCompileUnit();
            
            codeNamespace = new CodeNamespace("MyNamespace");
            codeCompileUnit.Namespaces.Add(codeNamespace);

            //neue Klasse, in der der Code ausgeführt wird
            codeClass = new CodeTypeDeclaration("MyClass");
            codeClass.Attributes = MemberAttributes.Public;
            codeClass.IsClass = true;
            codeClass.TypeAttributes = TypeAttributes.Public;
            codeNamespace.Types.Add(codeClass);
            codeNamespace.Imports.Add(new CodeNamespaceImport("System.Drawing"));
            
            //Main - Methode dient als Einstiegspunkt
            entryPoint = new CodeMemberMethod();
            entryPoint.Name = "Run";
            entryPoint.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            


            //Alle Assemblies aus der aktuellen Domäne hinzufügen. Das geschieht nur einmal, deswegen im Konstruktor
            libraries = new ArrayList();
            modules = new ArrayList();
            System.Reflection.Assembly[] domainAssemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (var lib in domainAssemblies)
            {
                if (lib.Location != String.Empty)
                {
                    AddLibrary(lib);
                }
            }

            MemberMethods = new List<CodeMemberMethod>();
        }

        /// <summary>
        /// Kompilieren des Codes
        /// </summary>
        public void Compile()
        {
            codeClass.Members.Clear();
            codeClass.Members.Add(entryPoint);
            entryPoint.Statements.Clear();
            entryPoint.Statements.Add(new CodeSnippetExpression(MainMethodCode));
            foreach (CodeMemberMethod method in MemberMethods)
            {
                codeClass.Members.Add(method);
            }

            //foreach (var module in modules)
            //{
            //    CodeMemberField field = new CodeMemberField();
            //    field.Name = (module as System.Windows.Forms.Form).Text;
            //    field.Type = new CodeTypeReference(module.GetType());
            //    field.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            //    //Einkommentieren für Fenster als Properties
            //    //codeClass.Members.Add(field);
            //}

            CompilerParameters compilerParameters = new CompilerParameters();
            String[] libStrings = new String[libraries.Count];
            int i = 0;
            foreach (Assembly libLocation in libraries)
            {
                libStrings[i++] = (String) libLocation.Location;
            }

            compilerParameters.ReferencedAssemblies.AddRange(libStrings);//.ToArray(typeof(String)));
            compilerParameters.GenerateInMemory = true;
            
            Microsoft.CSharp.CSharpCodeProvider codeProvider = new Microsoft.CSharp.CSharpCodeProvider();
            compilerResults = codeProvider.CompileAssemblyFromDom(compilerParameters, codeCompileUnit);
            
            //Code in Datei Schreiben
            TextWriter writer = new StreamWriter("code.txt");
            CodeGeneratorOptions codeGenOpt = new CodeGeneratorOptions();
            
            codeProvider.GenerateCodeFromCompileUnit(codeCompileUnit, writer, codeGenOpt);
            writer.Close();

            if(Debugger.IsAttached){
            System.Diagnostics.Process.Start("notepad.exe", "code.txt");
            }
            //Ende Code Schreiben

            if (compilerResults != null && compilerResults.Errors.Count > 0)
            {
                CompileError(this, new CompilerResultsEventArgs(compilerResults, codeNamespace.Name + "." + codeClass.Name, entryPoint.Name));
            }
            else
            {
                CompileComplete(this, new CompilerResultsEventArgs(compilerResults, codeNamespace.Name + "." + codeClass.Name, entryPoint.Name));
            }
        }


        public void AddModule(object module)
        {
            entryPoint.Parameters.Add(new CodeParameterDeclarationExpression(module.GetType(), (module as System.Windows.Forms.Form).Text));
            modules.Add(module);

        }


        /// <summary>
        /// Hinzufügen einer neuen Library
        /// </summary>
        /// <param name="path">Pfad zur Library, muss absolut sein</param>
        public void AddLibrary(Assembly lib)
        {
            if (libraries != null && !libraries.Contains(lib) && lib != null)
            {
                libraries.Add(lib);
            }
        }

        /// <summary>
        /// Löschen einer Library aus der Liste
        /// </summary>
        /// <param name="path"></param>
        public void RemoveLibrary(String path)
        {
            if (libraries.Contains(path))
            {
                libraries.Remove(path);
            }
        }
    }

    /// <summary>
    /// Erweiterte Event Arguments, die neben den Standardproperties die CompilerResults enthalten
    /// </summary>
    public class CompilerResultsEventArgs : EventArgs
    {
        public CompilerResultsEventArgs(CompilerResults results, String typeName, String entryPoint)
        {
            Results = results;
            TypeName = typeName;
            EntryPoint = entryPoint;
        }
        public CompilerResults Results
        {
            get;
            private set;
        }
        public String TypeName
        {
            get;
            private set;
        }
        public String EntryPoint
        {
            get;
            private set;
        }
    }
}
